// 强类型语言的作用, 可以直接声明或着说出函数或者变量的类型. 
(() => {
    function sendMsg(str: string):string {
        return "hello" + str;
    }
    let text = 'world';
    // let text = [1, 2, 3, 4]
    console.log(sendMsg(text));
    console.log('000000');


    // 类只能进行变量的定义. 
    class User {
        name: string;
        age: number;
        // toString():string;
    }

    // 接口可以定义方法, 但是类不能定义方法. 
    interface User1 {
        name: string,
        age: number,
        toString(): string
    }

    // null、undefined
    // 将null、undefined传递给其他类型是可以的. 
    let u: undefined;
    let num: number = u
    let str: String = u



    // void值不能传给其他基础类型变量. ts自动检测, 会编译时候就报错. 
    // js里面是不会报错的. 
    // let v:void
    // let num1:number = v
    // let str1:string = v


    // 还有空格是不会被加载进js代码里的. 
    let a: boolean = true
    // let b:number = a 
    // 不能把number类型的变量赋值给boolean类型

    // new Boolean()返回的是一个对象, 不是boolean值
    // let isDone:boolean = new Boolean(1); // 编译报错, 不能将Boolean类型给boolean.
    //  这里的Boolean(1)是返回的一个boolean值. 
    let idDone: boolean = Boolean(1)

    // 这样是可以的, 将String类型的值给String类型. 
    let isString:String = new String("111")

    // 数值类型
    let decLiteral: number = 6;

    // ts, js中只有一个数值对象, 没有java那样的多个数值, int、float、double. 
    let decimal:number = 6.66;
    let hexLiteral: number = 0xf00d;

    // ES6 中的二进制表示法
    let binaryLiteral: number = 0b1010;

    // ES6 中的八进制表示法
    let octalLiteral: number = 0o744;
    let notANumber: number = NaN;
    let infinityNumber: number = Infinity;

    // void, 空值, 没有什么用, 因为只能将null、undefined赋值给他.
    // null、undefined是所有基础类型的子类, 所以, 可以赋值给任何类型. 
    // 基本数据类型包括了: 
                        // null、undefined、boolean、number、string、object
                        // array、function、map等... 

    // 没有给类型的变量, 是默认的any类型, 后来可以赋任何值 
    let anyVarible;
    // string
    anyVarible = '12';
    // number
    anyVarible = 12
    
    // js如果初始化了, 但是没有给初始化类型, 那么声明的类型就是const, 不让改动的类型. 
    // ts就不让没有声明类型的变量存在. 这里会报错的. 
    // constVarible = '12';

    // 和变量一样, 如果, 没有给返回值类型, 那么可以返回各种, 和any一样. 
    function sendResult(string:string){
        // return '1111'
        return 111
    }

    // 类型推断: 就是在初始化没有声明变量类型, 但是给了初始化值了, 那么, 就会推断该变量就是这个类型的, 和范型有一点像. 
    // <T> 类型是不确定的, 只有在调用的使用, 才知道是什么类型. 

    // 断言
    function getLength2(param: string | number):number {
        // 直接利用length属性来判断是否有是string类型, 如果是, 那么就将length输出
        // 如果不是, 就先转成toString() 转成string类型的数据. 
        if((<string>param).length) {
            return (<string>param).length
        }else {
            return (<number>param).toString().length
        }
    }

    console.log('多个string1长度',getLength2('1111111111111'));
    console.log('多个number1长度', getLength2(1111111111111));
    

    // 联合类型
    function getLength(params: string | number) {
        return params.toString().length
    }

    // 这样两者是可以实现输出长度的, 但是, 如果本身是string类型了, 那么, 就不必要进行toString(), 
    // 类型断言就是告诉编译器, 我自己知道我在干嘛. 
    console.log(getLength(123));
    console.log(getLength('123'));

    // 类型断言
    function getLength1(params: string | number): number {
        // 告诉编译器, 我现在就是在string类型的参数上进行操作. 
        if ((<string>params).length) return (<string>params).length
        //这里告诉编译器, 我现在就是在number类型的参数上进行操作. 
        else return (<number>params).toString().length
    }


    /**
     * 接口对象模型
     * 接口就是用来进行对象定义的, 和java这类面向对象类的语言一样, 是行为的抽象. 
     * 
     * 接口里面的属性包括: 一般属性, 可选属性, 任意属性([propName:string]:any)
     * tips: 如果定义了任意类型, 那么, 确定和可选属性必须是他的子集. 
     * 
     * 
     * tip:有的语言会建议在接口的名称前面添加上大写字母 'I'. 
     * 定义的变量比接口少一些属性是不允许的, 和java中的抽象类, 必需将所有的抽象方法都实现一样, 不然就报错, 
     * 这里, 如果想要少变量或者多变量, 就需要写可选、任意属性了. 
     * 一般属性: 就是普通的属性. 
     * 可选属性, 就是‘?’修饰的属性. 
     * 任意属性, 就是 [propNames:string]:any, 这里的数据类型必须要和其他属性一致, 或者其他属性是这个拓展属性的子类, 不然就会报错. 
     * 只读属性, 就是readonly, 规定了只进行读, 也就是在第一次赋值了以后, 就不让改变值了, 否则会报错. (这里的报错都是编译报错)
     * 只读属性的约束是在第一次给对象赋值的情况下, 而不是, 第一次给只读属性赋值. 
     */
    interface Person {
        name: string,
        age: number
    }

    // 这里将tom规定为Person类型, 如果没有完全继承Person的属性和行为, 就会编译报错
    let tom: Person = {
        name: 'tom',
        age: 12
    }

    // 少于接口属性怎么写:

    interface IPerson {
        name: string,
        addr?: string, // 可选属性
        age: number,
    }
    // 多于接口属性怎么写: 
    // 少了接口里面的addr也是可以的.但是, 不知道后来的对象属性怎么实现, 所以很可能要写多个`?`可缺省类型. 
    let tomo: IPerson = {
        name: "tomo",
        age: 111
    }

    interface IPerson2 {
        name: string,
        age: number,
        [paramsName: string]: any   // 任意属性的名字和类型, 是允许的.  这样就可以多几个属性. 
    }

    let tomot: IPerson2 = {
        name: 'tomot',
        age: 12,
        addr: '上海市浦东新区'// 任意属性. 
    }


    // 这里就会报错, 只要有了任意类型, 那么, 该接口里面的所有属性, 必须是任意类型的子集. any => 所有的, null, undefined, boolean, number, string, object
    // string=> null, undefined, srting
    // boolean: boolean
    // number: null, undefined, number
    interface IPerson3 {
        name: string,
        // age?: number,  // 这里会报错, 编译时出错.  
        // length: number, // 这里报错, 编译时出错. 
        [prop: string]: string
    }

    // 只读属性
    interface IPerson4 {
        readonly id: number,
        name: string,
        age: number
    }

    let tommot: IPerson4 = {
        id: 12,
        name: 'tommot',
        age: 12
    }

    console.log(tommot);

    // Cannot assign to 'id' because it is a read-only property.
    // 不能赋值给只读属性, property
    // tommot.id = 12


    /**
     * 数组的定义: 数组定义的方法: 
     * 1. [类型+方括号]
     * 2. 范型
     */

    let arr: number[] = [1, 3, 4, 5]
    //  在定义的时候, 进行了类型的声明, 后续添加的数据就只能相同类型. 
    arr[arr.length] = 12
    // 给number类型的数组, 添加了非number类型的数据, 就会导致编译时报错. 
    //  arr[arr.length + 1] = 'aaa'

    let anyArr: any[] = ['111', 12, '122']
    // 还可以像解构赋值那样, 对不同位置的进行分别声明变量类型. 
    let anArr: [number, string, number] = [1, '11', 1]

    // 数组范型
    // 范型是在定义函数、接口或者类的时候, 不预先指定具体的类型, 而是在使用的时候再指定类型的一种特性. 
    let arr2: Array<number> = [1, 2, 3, 4, 5]

    // 不指定范型的类型, 只能在定义的方法的时候使用, 
    // 这里面的T指的是, 传递进来的参数的类型. 可以根据参数类型自动转换. 
    function createArray<T>(length: number, value: T): Array<T> {
        // 这里面的result的类型也必须要和传递进来的数据类型是一样的. 
        let result: T[] = []
        for (let i = 0; i < length; i++) {
            result[i] = value
        }
        return result
    }
    // 生成多个一样的数据, 但是他们的数据类型是一样的, 不过, 在使用之前, 任何类型都是可以的. 
    // 生成string类型的数组. 
    console.log(createArray(3, 'bob'));

    // 生成number类型的数组.  
    console.log(createArray(3, 12));


    // 也可以传多个不同类型的变量
    function returnVar<T, U>(params: [T, U]): [T, U] {
        return [params[0], params[1]];
    }

    /**
     * 范型约束: 在函数内部使用范型变量的时候, 由于不知道, 他会传进来什么类型的数据, 所以不能随意操作它的属性或者方法. 
     */

    // 这样的方法, 必须使用的是
    function logginIdentity<T>(param: T): T {
        //   type T 没有办法判断是否有方法里面有的属性. 
        //  console.log(param.length);

        return param
    }

    /**
     * 
     * 类数组: 不是数组, 只是类似数组,由K V组成, 但是, 没有length等属性. 
     */

     function argu(param:number):void{
         console.log('arguments',arguments);
         let arr:number[] = [1, 2, 3, 4]
         console.log('arr type', typeof arguments == "string");
         
         
        //   这里的arguments是一个对象, 不是array
         console.log('typeOf', typeof arguments);
         
     }
    argu(1111111)

    /**
     * 
     * IArguments类型是Ts内置的接口对象. 
     */
    //  let iArgumment:IArguments = {
    //      length:3,
    //  }

    /**
     * 声明合并
     */

    function reverse(x: number): number;
    function reverse(x: string): string;
    function reverse(x: number | string): number | String {
        if (typeof x === 'number') return Number(x.toString().split('').join);
        //   join 是数组的方法, 所以, 使用.split('')将其分开成数组,再使用数组的reverse()方法. 
        else if (typeof x === 'string') return x.split('').reverse().join('');
    }

    console.log(reverse(1));


    interface Photo {
        price: number,
        size: number
    }

    // 注意, 这里属性要唯一, 不能有多个属性. 
    // 和拓展属性一样, 需要子类型一致. 
    interface Photo {
        weight: number
    }

    // 必须完全实现Photo接口里面的属性. 
    let photo: Photo = {
        price: 12,
        size: 11,
        weight: 10
    }
    console.log(photo);


    interface Alarm {
        price: number,
        alert(s: string): string
    }

    interface Alarm {
        weight: number,
        alert(s: string, n: number): string
    }

    // let alarm:Alarm = {
    //     price: 11
    //     // weight:11,
    //     // alert(x:string, y:number) :string
    // }


    console.dir(Number);

    // let objs:Object[] = [
    //     {
    //         id: 1,
    //         name: 'bobo', 
    //         age:12
    //     },
    //     {
    //         id: 2,
    //         name: 'bobo1', 
    //         age:13
    //     },
    // ]

    // for (const item of objs) {
    //     // console.log(item.id);

    // }

    interface Item {
        id: number,
        name: string,
        age: number
    }

    let Items: Array<Item> = [
        {
            id: 4,
            name: 'bob',
            age: 21
        },
        {
            id: 3,
            name: 'bob',
            age: 21
        },
        {
            id: 2,
            name: 'bob',
            age: 21
        },
        {
            id: 1,
            name: 'bob',
            age: 21
        },
        {
            id: 2,
            name: 'bob',
            age: 21
        },
        {
            id: 2,
            name: 'bob',
            age: 21
        },
    ]

    Items.forEach(item => {
        console.log(item);

        if (item.id === 1) {
            console.log('1');
            return
        }
    })

    // ts里面没有map
    // let myMap = new Map()

    interface IPerson5 {
        name: string,
        age: number,
        // 这里的返回值是string, 后面实现的话, 就得是string. 
        sayHi: () => string
    }

    let tmo: IPerson5 = {
        // 类中, 还是需要有逗号的. 
        name: 'tmo',
        age: 12,
        sayHi: () => { return '90' }
    }

    let arr11: IPerson5 = {
        // 类中, 还是需要有逗号的. 
        name: 'tmo',
        age: 12,
        sayHi: () => { return '90' }
    }


    /**
     * 函数声明
     */

     function sum(x, y) {
         return x + y
     }

     console.log('1+2',sum('1', 2)); // 输出结果: '12'
     console.log('Number(1)+2',sum(Number('1'), 2)); // 输出结果: '12'
     console.log('1 + 2',sum(1, 2)); // 输出结果:  3
     

    //  function sum(x:number, y: number) {

    //  }


})()